跳到主要内容

节点脚本(Node Scripts)

节点脚本(Node Scripts)可用于渲染形状、图像、文本、画板等内容。

创建节点脚本

  1. 创建新脚本 并选择 节点(Node)
  2. 将其添加到场景

脚本结构(Anatomy)

-- Define the script's data and inputs.
type MyNode = {}

-- Called once when the script initializes.
function init(self: MyNode): boolean
return true
end

-- Called every frame to advance the simulation.
-- 'seconds' is the elapsed time since the previous frame.
function advance(self: MyNode, seconds: number): boolean
return false
end

-- Called when any input value changes.
function update(self: MyNode) end

-- Called every frame (after advance) to render the content.
function draw(self: MyNode, renderer: Renderer) end


-- Return a factory function that Rive uses to build the Node instance.
return function(): Node<MyNode>
return {
init = init,
advance = advance,
update = update,
draw = draw,
}
end

绘制(Drawing)

节点脚本允许在场景中按程序方式绘制图形。

function rectangle(self: Rectangle)
-- Update the path with current width and height
self.path:reset()

local halfWidth = self.width / 2
local halfHeight = self.height / 2

-- Draw rectangle centered at origin
self.path:moveTo(Vector.xy(-halfWidth, -halfHeight))
self.path:lineTo(Vector.xy(halfWidth, -halfHeight))
self.path:lineTo(Vector.xy(halfWidth, halfHeight))
self.path:lineTo(Vector.xy(-halfWidth, halfHeight))
self.path:close()

-- Update paint color
self.paint.color = self.color
end

function draw(self: Rectangle, renderer: Renderer)
renderer:drawPath(self.path, self.paint)
end

完整绘图 API 参考:Path API

常见模式(Common Patterns)

运行时实例化组件(Instantiating Components)

该模式需要了解 数据绑定(Data Binding)组件(Components)脚本输入(Script Inputs)

type Enemy = {
artboard: Artboard<Data.Enemy>,
position: Vector,
}

export type MyGame = {
-- This is the component that we will dynamically add to our scene
-- See: https://rive.app/docs/scripting/script-inputs
enemy: Input<Artboard<Data.Enemy>>,
enemies: { Enemy },
}

function createEnemy(self: MyGame)
-- Create an instance of the artboard
local enemy = self.enemy:instance()

-- Keep track of all enemies in self.enemies
local entry: Enemy = {
artboard = enemy,
position = Vector.xy(0, 0),
}
table.insert(self.enemies, entry)
end

function init(self: MyGame)
createEnemy(self)

return true
end

function advance(self: MyGame, seconds: number)
-- Advance the artboard of each enemy
for _, enemy in self.enemies do
enemy.artboard:advance(seconds)
end

return true
end

function draw(self: MyGame, renderer: Renderer)
-- draw each enemy
for _, enemy in self.enemies do
renderer:save()
enemy.artboard:draw(renderer)
renderer:restore()
end
end

return function(): Node<MyGame>
return {
init = init,
advance = advance,
draw = draw,
enemy = late(),
enemies = {},
}
end

固定步进(Fixed-Step Advance)

不同设备帧率不同,若直接用可变帧时间更新,会导致运动不一致。

可使用固定时间步(fixed timestep)推进模拟,保证行为稳定。

--- Fixed Timestep Advance
--- Keeps movement consistent across different frame rates
--- by advancing the simulation in fixed time steps.
export type CarGame = {
speed: Input<number>,
accumulator: number,
fixedStep: Input<number>,
direction: number,
currentX: number,
currentY: number,
}

-- Prevent the script from running too many catch-up steps
-- after a long pause or frame drop.
local MAX_STEPS = 5

function advance(self: CarGame, seconds: number): boolean
-- Add the time since the last frame to the accumulator.
self.accumulator += seconds

local dt = self.fixedStep
local steps = 0

-- Run the simulation in small, fixed steps.
-- If the frame took longer than one step, multiple steps may run this frame.
while self.accumulator >= dt and steps < MAX_STEPS do
-- Move forward by speed * time.
-- Using a fixed dt keeps movement stable even if the frame rate changes.
self.currentX += self.speed * math.cos(self.direction) * dt
self.currentY += self.speed * math.sin(self.direction) * dt

-- Subtract one fixed step from the accumulator
-- and repeat until we've caught up to real time.
self.accumulator -= dt
steps += 1
end

return true
end

-- Create a new instance of the CarGame script with default values.
-- The simulation runs 60 fixed steps per second.
return function(): Node<CarGame>
return {
speed = 100,
accumulator = 0,
direction = 0,
fixedStep = 1 / 60,
currentX = 0,
currentY = 0,
}
end